package mGraph;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.Set;
import java.util.Vector;

import mGraph.utils.TimestampConverter;


/**
 * 
 * @author Giulio Rossetti
 *
 */
public class MTGraph implements Serializable{
	
	
	private static final long serialVersionUID = -5136356165562611622L;
	protected Hashtable<Integer,MTNode> nodes;
	private int dimension;
	private int timeinterval;
	
	
	/**
	 * 
	 * Initialize a Multidimensional-Timestamped Graph
	 * 
	 * @param dimension Number of available dimensions for the network
	 * @param timeinterval Number o the available temporal timestamps for the network
	 */
	public MTGraph(int dimension, int timeinterval){
		this.nodes = new Hashtable<Integer,MTNode>();
		this.dimension = dimension;
		this.timeinterval = timeinterval;
	}
	
	
	/**
	 * Return the set of all the Identifiers of the nodes in the Graph 
	 * 
	 * @return Set of Identifiers
	 */
	public Set<Integer> getNodes(){
		return this.nodes.keySet();
	}
	
	
	/**
	 * Return the dimensionality of the Graph 
	 * 
	 * @return Number of available dimensions
	 */
	public int getDimension(){
		return this.dimension;
	}
	
	
	/**
	 * Add a node to the Graph
	 * 
	 * @param node An MTNode
	 */
	public void addNode(MTNode node){
		if(!this.nodes.containsKey(node.getId())){
			this.nodes.put(node.getId(),node);
		}
	}
	
	
	/**
	 * Add a set of nodes to the Graph
	 * 
	 * @param nodes A set of MTNode 
	 */
	public void addNodes(Set<MTNode> nodes){
		for(MTNode n:nodes){
			if(!this.nodes.containsKey(n.getId())){
				this.nodes.put(n.getId(),n);
			}
		}
	}
	
	
	/**
	 * Add an Edge to the Graph
	 * 
	 * @param x MTNode endpoint of the edge
	 * @param y MTNode endpoint of the edge
	 * @param dim Dimension to which belongs the edge
	 * @param timestamp Timestamp of appearance of the edge
	 */
	public void addEdge(MTNode x, MTNode y, int dim,int timestamp){
		if(this.nodes.containsKey(x.getId())){
			MTNode start = this.nodes.get(x.getId());
			start.addEdge(y, dim, timestamp, timeinterval);
		}
		else {
			this.nodes.put(x.getId(), x);
			x.addEdge(y, dim, timestamp, timeinterval);
		}
		
		if(this.nodes.containsKey(y.getId())){
			MTNode end = this.nodes.get(y.getId());
			end.addEdge(x, dim, timestamp, timeinterval);
		}
		else {
			this.nodes.put(y.getId(), y);
			y.addEdge(x, dim, timestamp, timeinterval);
		}
		
	}
	
	/**
	 * Return the desired MTNode if present
	 * 
	 * @param id Identifier of the node
	 * @return MTNode
	 */
	public MTNode getNode(int id){
		return this.nodes.get(id);
	}
	
	
	/**
	 * Return the edges of the graph that belongs to the given dimension
	 * 
	 * @param d Identifier of the dimension of interest
	 * @return
	 */
	public Vector<MTEdge> getEdgesOnDimension(Integer d){
		Vector<MTEdge> edges = new Vector<MTEdge>();
		
		Set<Integer> nodes_ids = this.nodes.keySet();
		
		for(Integer id_n: nodes_ids){
			MTNodeSet nodes = this.nodes.get(id_n).getNeighboursSet(d);
			if(nodes!=null){
				for(Integer n:nodes.getNodes()){
					if(this.getNode(n).getId()>id_n){
						int timestamp = nodes.getTimestamp(n);
						TimestampConverter tsc = new TimestampConverter();
						
						
						MTEdge e = new MTEdge(id_n,this.getNode(n).getId(),d,tsc.intToByteArray(timestamp));
						edges.add(e);
					}
				}
			}
			
		}
		
		return edges;
	}
	
	/**
	 * Compute the cardinality of the edges that belongs to the given dimensions
	 * 
	 * @param d Identifier of a dimension
	 * @return
	 */
	public int getEdgeOnDimensionCount(int d){
		Set<Integer> nodes_ids = this.nodes.keySet();
		int total_over_d = 0;
		
		for(Integer id_n: nodes_ids){
			MTNodeSet app = this.nodes.get(id_n).getNeighboursSet(d);
			if(app!=null)
				total_over_d = total_over_d + this.nodes.get(id_n).getNeighboursSet(d).getNodes().size();
		}
		return total_over_d;
	}
	
	
	/**
	 * Compute the ratio between the edges that belongs to a given dimension and the overall
	 * number of edges in the graph
	 * 
	 * @param d Identifier of a dimension
	 * @return
	 */
	public double getEdgeDimensionDegreeOverTotal(int d){
		
		Set<Integer> nodes_ids = this.nodes.keySet();
		int total = 0;
		int total_over_d = 0;
		
		for(Integer id_n: nodes_ids){
			total = total + this.nodes.get(id_n).getNeighbours().getNodes().size();
			MTNodeSet app = this.nodes.get(id_n).getNeighboursSet(d);
			if(app!=null)
				total_over_d = total_over_d + this.nodes.get(id_n).getNeighboursSet(d).getNodes().size();
		}
		
		return (double) total_over_d/total;
	}
	
	
	/**
	 * Calcola il rapporto dei nodi appartenenti alla dimensione passata come parametro
	 * rispetto al totale dei nodi
	 * 
	 * @param d
	 * @return
	 */
	public double getNodeDimensionDegreeOverTotal(int d){
		Set<Integer> nodes_ids = this.nodes.keySet();
		int total = nodes.size();
		int total_over_d = 0;
		
		for(Integer id_n: nodes_ids){
			
			MTNodeSet app = this.nodes.get(id_n).getNeighboursSet(d);
			if(app!=null)
				total_over_d = total_over_d + 1;
		}
		
		return (double) total_over_d/total;
	}
	
	
	/**
	 * Return the cardinality of the edges that belongs to the Graph. 
	 * 
	 * @param timestamped True if for any edge we are interested on all the timestamped occurrence, false otherwise 
	 * (an edge are counted once for any dimensions it belongs).
	 * @return
	 */
	public int getTotalEdgesNumber(boolean timestamped) {
		int total = 0;
		Set<Integer> nodes_ids = this.nodes.keySet();
		
		for(Integer id_n: nodes_ids){
			MTNode n = this.getNode(id_n);
			for(int d=0; d<this.dimension;d++){
				if(n.getNeighboursSet(d)!=null)
					if (!timestamped)
						total = total + n.getNeighboursSet(d).getNodes().size();
					else{
						MTNodeSet nodes_temp = n.getNeighboursSet(d);
						for(Integer mtn: nodes_temp.getNodes()){
							int times = nodes_temp.getTimestamp(mtn);
							TimestampConverter tsc = new TimestampConverter();
							byte[] interaction = tsc.intToByteArray(times);
							for(int i=0;i<interaction.length;i++){
								if(interaction[i]!=0) total = total+1;
							}
						}
						
					}
			}
		}
		
		return total/2;
	}
	

	/**
	 * Serializza il grafo
	 * 
	 * @param namefile
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public void Serialize(String namefile) throws FileNotFoundException, IOException{
		 ObjectOutput out = new ObjectOutputStream(new FileOutputStream(namefile));
		 out.writeObject(this); 
		 out.close();
	}

	
	/**
	 * Legge un grafo serializzato
	 * 
	 * @param namefile
	 * @throws FileNotFoundException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public void ReadSerialized(String namefile) throws FileNotFoundException, IOException, ClassNotFoundException{
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(namefile));
		MTGraph g = (MTGraph) ois.readObject();
		this.dimension = g.getDimension();
		Set<Integer> node_ids = g.getNodes();
		
		Hashtable<Integer,MTNode> nodes = new Hashtable<Integer,MTNode>();
		
		for(Integer id:node_ids){
			nodes.put(id, g.getNode(id));
		}
		
		this.nodes = nodes;
	}
	
	
	public String toString(){
		Set<Integer> i = this.nodes.keySet();
		for(Integer key: i){
			System.out.println("Node: "+key);
			this.nodes.get(key).toString();
		}
		return null;
	}
	
}
